//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================

/* Simulating sound card driver in NT virtual machine
 * Ver. 1.0 Add basic audio out functions, By Leng Bo, 2004-12-27
 * Ver. 1.1 Add basic audio in  functions, By Leng Bo, 2005-1-5
 */

#include <assert.h>
#include <stdio.h>
#include <windows.h>
#include <Mmsystem.h>

#include "_audio.h"

#define OUTBUF_COUNT        8
#define INBUF_COUNT         8
#define AUDIOOUT_BUFSIZE    0x1000
#define AUDIOIN_BUFSIZE     0x1000
#define WAIT_TIME           8000
#define WM_AUDIOIN_NOTIFY   WM_USER + 1

#define NEXT_OUT_BUFFER(idx) ((++idx) % OUTBUF_COUNT)
#define NEXT_IN_BUFFER(idx)  ((++idx) % INBUF_COUNT)

typedef struct{
    char buffer[AUDIOOUT_BUFSIZE];
    INT length;
    WAVEHDR header;
} AUDIOOUT_BUFFER;

typedef struct{
    char buffer[AUDIOIN_BUFSIZE];
    WAVEHDR header;
} AUDIOIN_BUFFER;

// For AudioOut (Playing)
static HWAVEOUT         g_phwoAudioOut = NULL;
static AUDIOOUT_BUFFER  g_bufferAudioOut[OUTBUF_COUNT] = {0};
static HANDLE           g_hPulseAudioOut = NULL;
static INT              g_playingBufCnt = 0;
static CRITICAL_SECTION g_playingBufCntLock;   //lock for g_playingBufCnt

// For AudioIn (Recording)
static HWAVEIN          g_phwoAudioIn = NULL;
static AUDIOIN_BUFFER   g_bufferAudioIn[INBUF_COUNT] = {0};
static HANDLE           g_hPulseAudioIn = NULL;
static INT              g_curReadBuf = 0; // Current reading buffer index
static INT              g_curReadBufPos = 0; // position of current reading buffer
static INT              g_filledBufCnt = 0;
static CRITICAL_SECTION g_filledBufCntLock;   //lock for g_filledBufCnt

// Check the sample rate: Is it supported by system?
static UINT CheckRate(UINT rate)
{
    switch (rate) {
        case 8000:
        case 11025:
        case 16000:
        case 22050:
        case 24000:
        case 32000:
        case 44100:
        case 48000:
            return rate;
        default:
            return 48000; // default value for AC97
    }
}

static void CALLBACK
waveOutProc(HWAVEOUT hwo,
    UINT uMsg,
    DWORD dwInstance,
    DWORD dwParam1,
    DWORD dwParam2)
{
    DBGOUT(Dprintf(L"waveOutProc uMsg = 0x%x.\n", uMsg));
    if (uMsg == WOM_DONE) {
        DBGOUT(Dprintf(L"WOM_DONE\n"));
        EnterCriticalSection(&g_playingBufCntLock);
        g_playingBufCnt--;
        LeaveCriticalSection(&g_playingBufCntLock);
        ::SetEvent(g_hPulseAudioOut);
    }
    return ;
}

// Set the volume of the output audio stream.
// In Elastos sound card drivers,
// the volume in each channel occupies 8 bit;
// In Windows, it occupies 16 bit.
// So it need to be converted.
EXTERN_C void AudioOutSetVolume(UINT16 volume)
{
    UINT16 leftVol = (volume & 0x00FF) << 8;
    UINT16 rightVol = volume & 0xFF00;
    DWORD winVolume = (UINT32)leftVol | ((UINT32)rightVol << 16);
    waveOutSetVolume(g_phwoAudioOut, winVolume);
    DBGOUT(Dprintf(L"AudioOutSetVolume: From 0x%x to 0x%x\n", volume, winVolume));
}

// Initialize the audio device for playing.
// If you want to use sound card properly, you must invoke it!
EXTERN_C INT AudioOutInitial(UINT Rate, UINT16 BitsPerSample, UINT8 Channels)
{
    WAVEFORMATEX waveformat;

    waveformat.wFormatTag = WAVE_FORMAT_PCM;
    waveformat.nChannels = (Channels == 1) ? 1 : 2; // 1 or 2
    waveformat.nSamplesPerSec = CheckRate(Rate); // Only set the supported rate
    waveformat.wBitsPerSample = (BitsPerSample == 8) ? 8 : 16; // Usually 8 or 16
    waveformat.nBlockAlign = Channels * BitsPerSample;
    waveformat.nAvgBytesPerSec = Rate * waveformat.nBlockAlign;
    waveformat.cbSize = 0;

    INT ret;

    for (UINT i = 0; i < OUTBUF_COUNT; i++) {
        g_bufferAudioOut[i].header.dwFlags |= WHDR_DONE;
    }

    if (!waveOutGetNumDevs()) {
        DBGOUT(Dprintf(L"There is no channel for use now.\n"));
        return E_FAIL;
    }

    if (g_phwoAudioOut) {
        AudioOutClose();
    }

    // Open the audio device
    ret = waveOutOpen(&g_phwoAudioOut,
        WAVE_MAPPER,
        &waveformat,
        (DWORD)waveOutProc,
        0,
        CALLBACK_FUNCTION);

    if (ret == MMSYSERR_NOERROR) {
        g_playingBufCnt = 0;
        DBGOUT(Dprintf(L"Successfully in opening audio device.\n"));
    }
    else {
        DBGOUT(Dprintf(L"Error in opening audio device, ret = 0x%x.\n", ret));
        return ret;
    }

    InitializeCriticalSection(&g_playingBufCntLock);

    return NO_ERROR;
}

static HRESULT AudioOutWrite(UINT);
// Put the audio data to driver buffers
EXTERN_C HRESULT AudioOutBufCache(byte *pBuffer, UINT Size, UINT *pSizeWritten)
{
    HRESULT hr;
    if (Size > AUDIOOUT_BUFSIZE) Size = AUDIOOUT_BUFSIZE;

    EnterCriticalSection(&g_playingBufCntLock);
    assert(g_playingBufCnt <= OUTBUF_COUNT);
    if (g_playingBufCnt == OUTBUF_COUNT) {
        LeaveCriticalSection(&g_playingBufCntLock);
        // Buffer maybe full, so it should wait here.
        DBGOUT(Dprintf(L"Here wait to fill a buffer.\n"));
        // Wait here , the max time is WAIT_TIME ms.
        // Ordinarily WOM_DONE message will set it to signaled state.
        // If there is no WOM_DONE(very little possibility),
        // WAIT_TIME ms later it will come back:)
        Wait:
        DWORD dwReason=WaitForSingleObject(g_hPulseAudioOut, WAIT_TIME);
        if (dwReason == WAIT_TIMEOUT) {
            DBGOUT(Dprintf(L"Time is out\n"));
        }
        else if (dwReason == WAIT_OBJECT_0) {
            DBGOUT(Dprintf(L"signaled\n"));
        }
        else if (dwReason == WAIT_FAILED) {
            DBGOUT(Dprintf(L"failed\n"));
            DBGOUT(Dprintf(L"error code %d\n",GetLastError()));
        }
        else
            DBGOUT(Dprintf(L"not signaled,but what?%d\n",dwReason));
    }
    else {
        LeaveCriticalSection(&g_playingBufCntLock);
    }
    // There are some buffers that can be used, so let's go!
    UINT i=0;
    for (; i < OUTBUF_COUNT; i++) {
        if (g_bufferAudioOut[i].header.dwFlags & WHDR_DONE) {
            DBGOUT(Dprintf(L"Here find buffer %d to fill.\n", i));
            memcpy(g_bufferAudioOut[i].buffer, pBuffer, Size);
            g_bufferAudioOut[i].length = Size;
            hr = AudioOutWrite(i);
            if (FAILED(hr)) {
                g_bufferAudioOut[i].header.dwFlags |= WHDR_DONE;
                return hr;
            }
            break;
        }
    }

    // g_hPulseAudioOut may be signaled in other conditions
    //  other than SetEvent in function waveOutProc.
    // So if there are no buffers to use,
    // it will still wait here.
    if (i == OUTBUF_COUNT) {
        i = 0;
        printf("ThreadID is %d\n",GetCurrentThreadId());
        goto Wait;
    }
    assert(i < OUTBUF_COUNT);
    *pSizeWritten = Size;
    return NO_ERROR;
}

static HRESULT AudioOutWrite(UINT index)
{
    INT ret;

    WAVEHDR *pHeader = &g_bufferAudioOut[index].header;
    pHeader->dwFlags &= ~WHDR_DONE;
    pHeader->lpData = g_bufferAudioOut[index].buffer;
    pHeader->dwBufferLength = g_bufferAudioOut[index].length;
    pHeader->dwBytesRecorded = 0;
    pHeader->dwUser = 0;
    pHeader->dwFlags = 0;
    pHeader->dwLoops = 0;

    DBGOUT(Dprintf(L"AudioOutWrite, buffer %d, length 0x%x.\n", index, pHeader->dwBufferLength));

    ret =  waveOutPrepareHeader(g_phwoAudioOut, pHeader, sizeof(WAVEHDR));
    if (ret == MMSYSERR_NOERROR) {
        DBGOUT(Dprintf(L"Successfully in preparing audio device.\n"));
    }
    else {
        DBGOUT(Dprintf(L"Error in preparing audio device, ret = 0x%x.\n", ret));
        return E_FAIL;
    }

    ret = waveOutWrite(g_phwoAudioOut, pHeader, sizeof(WAVEHDR));
    if (ret == MMSYSERR_NOERROR) {
        EnterCriticalSection(&g_playingBufCntLock);
        g_playingBufCnt++;
        LeaveCriticalSection(&g_playingBufCntLock);
        DBGOUT(Dprintf(L"Successfully in writing audio device.\n"));
    }
    else {
        DBGOUT(Dprintf(L"Error in writing audio device, ret = 0x%x.\n", ret));
        return E_FAIL;
    }

    return NO_ERROR;
}

unsigned long ElaSerialID;
EXTERN_C INT AudioDeviceInitialize(void)
{
    GetVolumeInformationW(L"C:\\",NULL,0,&ElaSerialID,NULL,NULL,NULL,0);
    printf("ElaSerialID: %08x\n", ElaSerialID);

    DBGOUT(Dprintf(L"AudioDeviceInitialize.\n"));
    DBGOUT(Dprintf(L"sizeof(g_bufferAudioOut) = %d.\n", sizeof(g_bufferAudioOut)));
    memset(g_bufferAudioOut, 0, sizeof(g_bufferAudioOut));
    memset(g_bufferAudioIn, 0, sizeof(g_bufferAudioIn));

    g_hPulseAudioOut = ::CreateEventW(NULL,
                           FALSE,
                           FALSE,
                           L"NT_AudioOut_Cond");

    if (g_hPulseAudioOut == NULL) {
        DBGOUT(Dprintf(L"CreateEventW error!\n"));
        return -1;
    }

    g_hPulseAudioIn = ::CreateEventW(NULL,
                           FALSE,
                           FALSE,
                           L"NT_AudioIn_Cond");

    if (g_hPulseAudioIn == NULL) {
        DBGOUT(Dprintf(L"CreateEventW error!\n"));
        return -1;
    }

    DBGOUT(Dprintf(L"Initial AC97 Successfully!\n"));
    return 0;
}

EXTERN_C void AudioOutReset()
{
    DBGOUT(Dprintf(L"AudioOutReset.\n"));
    waveOutReset(g_phwoAudioOut);
    EnterCriticalSection(&g_playingBufCntLock);
    g_playingBufCnt = 0;
    LeaveCriticalSection(&g_playingBufCntLock);
}

EXTERN_C void AudioOutClose()
{
    DBGOUT(Dprintf(L"AudioOutClose.\n"));
    // If it is playing now, wait for all the buffers to be played.
    while (1) {
        EnterCriticalSection(&g_playingBufCntLock);
        if (g_playingBufCnt <= 0) {
            LeaveCriticalSection(&g_playingBufCntLock);
            break;
        }
        else {
            LeaveCriticalSection(&g_playingBufCntLock);
        }
        WaitForSingleObjectEx(g_hPulseAudioOut, WAIT_TIME, FALSE);
        DBGOUT(Dprintf(L"Wait for all buffers to be played.\n"));
    }
    waveOutReset(g_phwoAudioOut);
    for (UINT i = 0; i < OUTBUF_COUNT; i++) {
        waveOutUnprepareHeader(g_phwoAudioOut, &g_bufferAudioOut[i].header, sizeof(WAVEHDR));
    }
    DeleteCriticalSection(&g_playingBufCntLock);
    waveOutClose(g_phwoAudioOut);
    g_phwoAudioOut = NULL;
}

static void CALLBACK
waveInProc(HWAVEOUT hwo,
    UINT uMsg,
    DWORD dwInstance,
    DWORD dwParam1,
    DWORD dwParam2)
{
    DBGOUT(Dprintf(L"waveInProc uMsg = 0x%x.\n", uMsg));
    if (uMsg == WIM_DATA) {
        DBGOUT(Dprintf(L"WIM_DATA\n"));
        EnterCriticalSection(&g_filledBufCntLock);
        g_filledBufCnt++;
        if (g_filledBufCnt == 1)
            ::SetEvent(g_hPulseAudioIn);
        LeaveCriticalSection(&g_filledBufCntLock);
//        PostThreadMessage(g_audioInMonitorThdId, WM_AUDIOIN_NOTIFY, dwParam1, dwParam2);
    }
    return ;
}

// Initialize the audio device for recording.
// If you want to use sound card properly, you must invoke it!
EXTERN_C INT AudioInInitial(UINT Rate, UINT16 BitsPerSample, UINT8 Channels)
{
    WAVEFORMATEX waveformat;

    waveformat.wFormatTag = WAVE_FORMAT_PCM;
    waveformat.nChannels = (Channels == 1) ? 1 : 2; // 1 or 2
    waveformat.nSamplesPerSec = CheckRate(Rate); // Only set the supported rate
    waveformat.wBitsPerSample = (BitsPerSample == 8) ? 8 : 16; // Usually 8 or 16
    waveformat.nBlockAlign = Channels * BitsPerSample;
    waveformat.nAvgBytesPerSec = Rate * waveformat.nBlockAlign;
    waveformat.cbSize = 0;

    INT ret;
    UINT i;

    if (!waveInGetNumDevs()) {
        DBGOUT(Dprintf(L"There is no channel for use now.\n"));
        return E_FAIL;
    }

    if (g_phwoAudioIn) {
        AudioInClose();
    }

    // Open the audio device
    ret = waveInOpen(&g_phwoAudioIn,
        WAVE_MAPPER,
        &waveformat,
        (DWORD)waveInProc,
        0,
        CALLBACK_FUNCTION);

    if (ret == MMSYSERR_NOERROR) {
        DBGOUT(Dprintf(L"Successfully in opening audio device.\n"));
    }
    else {
        DBGOUT(Dprintf(L"Error in opening audio device, ret = 0x%x.\n", ret));
        return ret;
    }

    InitializeCriticalSection(&g_filledBufCntLock);

    for (i = 0; i < INBUF_COUNT; i++) {
        ret = AudioInAddBuffer(i);
        if (ret != NOERROR) return ret;
    }

    g_filledBufCnt = 0;

    ret = waveInStart(g_phwoAudioIn);
    if (ret == MMSYSERR_NOERROR) {
        DBGOUT(Dprintf(L"Successfully in starting recording.\n"));
    }
    else {
        DBGOUT(Dprintf(L"Error in recording, ret = 0x%x.\n", ret));
        return E_FAIL;
    }

    g_curReadBuf = 0;
    g_curReadBufPos = 0;

    return NOERROR;
}

// Get the audio data from driver buffers
EXTERN_C HRESULT AudioInBufCache(byte *pBuffer, UINT Size, UINT *pSizeRead)
{
    EnterCriticalSection(&g_filledBufCntLock);
    if (g_filledBufCnt == 0) {
        LeaveCriticalSection(&g_filledBufCntLock);
        // No buffer to read, so should wait here
        DBGOUT(Dprintf(L"Here wait to get a filled buffer."));
        // Wait here , the max time is WAIT_TIME ms.
        // Ordinarily WIM_DONE message will set it to signaled state.
        // If there is no WIM_DONE(very little possibility),
        // WAIT_TIME ms later it will come back:)
        Wait:
        DWORD dwReason = WaitForSingleObjectEx(g_hPulseAudioIn, WAIT_TIME, FALSE);
        if (dwReason == WAIT_TIMEOUT) {
            // No buffers to use after waiting, perhaps the device isn't open.
            DBGOUT(Dprintf(L"Time is out.g_filledBufCnt=%d\n", g_filledBufCnt));
            return E_ACCESSDENIED;
        }
        // g_hPulseAudioIn may be signaled in other conditions
        // other than SetEvent in function waveInProc.
        // So if there are no buffers to use,
        // it will still wait here.
        EnterCriticalSection(&g_filledBufCntLock);
        if (g_filledBufCnt == 0) {
            LeaveCriticalSection(&g_filledBufCntLock);
            goto Wait;
        }
        else {
            LeaveCriticalSection(&g_filledBufCntLock);
        }
    }
    else {
        LeaveCriticalSection(&g_filledBufCntLock);
    }

    UINT MaxCnt = AUDIOIN_BUFSIZE - g_curReadBufPos;
    if (Size > MaxCnt) Size = MaxCnt;

    DBGOUT(Dprintf(L"AudioInBufCache, buffer %d, MaxCnt: 0x%x," \
        L"curReadPos: 0x%x, Read length: 0x%x.\n",
        g_curReadBuf, MaxCnt, g_curReadBufPos, Size));

    memcpy(pBuffer,g_bufferAudioIn[g_curReadBuf].buffer + g_curReadBufPos,
        Size);

    g_curReadBufPos += Size;

    if (Size == MaxCnt) {
        // All the content of this buffer has been read.
        // So get the next buffer
        AudioInAddBuffer(g_curReadBuf);
        g_curReadBuf = NEXT_IN_BUFFER(g_curReadBuf);
        g_curReadBufPos = 0;
        EnterCriticalSection(&g_filledBufCntLock);
        g_filledBufCnt--;
        LeaveCriticalSection(&g_filledBufCntLock);
        DBGOUT(Dprintf(L"Now Switch to buffer %d.\n", g_curReadBuf));
    }
    *pSizeRead = Size;
    return NOERROR;
}

EXTERN_C HRESULT AudioInAddBuffer(UINT index)
{
    WAVEHDR *pHeader = &g_bufferAudioIn[index].header;
    pHeader->dwFlags &= ~WHDR_DONE;
    pHeader->lpData = g_bufferAudioIn[index].buffer;
    pHeader->dwBufferLength = AUDIOIN_BUFSIZE;
    pHeader->dwBytesRecorded = 0;
    pHeader->dwUser = index; // the index of the buffer, reserved
    pHeader->dwFlags = 0;
    pHeader->dwLoops = 0;

    INT ret =  waveInPrepareHeader(g_phwoAudioIn, pHeader, sizeof(WAVEHDR));
    if (ret == MMSYSERR_NOERROR) {
        DBGOUT(Dprintf(L"Successfully in preparing buffer %d.\n", index));
    }
    else {
        DBGOUT(Dprintf(L"Error in preparing buffer %d, ret = 0x%x.\n", index, ret));
        return E_FAIL;
    }

    ret = waveInAddBuffer(g_phwoAudioIn, pHeader, sizeof(WAVEHDR));
    if (ret == MMSYSERR_NOERROR) {
        DBGOUT(Dprintf(L"Successfully in adding buffer %d.\n", index));
    }
    else {
        DBGOUT(Dprintf(L"Error in adding buffer %d, ret = 0x%x.\n", index, ret));
        return E_FAIL;
    }

    return NOERROR;
}

EXTERN_C void AudioInReset()
{
    DBGOUT(Dprintf(L"AudioInReset.\n"));
    waveInReset(g_phwoAudioIn);
    g_curReadBuf = 0; // Current reading buffer index
    g_curReadBufPos = 0; // position of current reading buffer
    EnterCriticalSection(&g_filledBufCntLock);
    g_filledBufCnt = 0;
    LeaveCriticalSection(&g_filledBufCntLock);

    for (int i = 0; i < INBUF_COUNT; i++) {
        AudioInAddBuffer(i);
    }
    waveInStart(g_phwoAudioIn);
}

EXTERN_C void AudioInClose()
{
    DBGOUT(Dprintf(L"AudioInClose.\n"));
    waveInReset(g_phwoAudioIn);
    for (UINT i = 0; i < INBUF_COUNT; i++) {
        waveInUnprepareHeader(g_phwoAudioIn, &g_bufferAudioOut[i].header, sizeof(WAVEHDR));
    }
    DeleteCriticalSection(&g_filledBufCntLock);
    waveInClose(g_phwoAudioIn);
    g_phwoAudioIn = NULL;
}
